import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, Input
from keras.optimizers import Adam

# 1. 生成示例数据（假设有30天的销量数据）
dates = pd.date_range(start='2023-05-01', periods=30, freq='D')
sales = np.array([142	,
146	,
95	,
79	,
84	,
70	,
94	,
70	,
67	,
62	,
48	,
75	,
54	,
34	,
40	,
113	,
68	,
44	,
47	,
28	,
50	,
45	,
48	,
50	,
32	,
25	,
32	,
32	,
38	,
61
])

# 创建DataFrame并添加星期特征
data = pd.DataFrame({'Date': dates, 'Sales': sales})
data['DayOfWeek'] = data['Date'].dt.dayofweek  # 添加星期几作为额外特征
data.set_index('Date', inplace=True)

# 2. 数据预处理
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data[['Sales', 'DayOfWeek']])


# 3. 创建训练数据集
def create_dataset(data, time_step=1):
    X, y = [], []
    for i in range(len(data) - time_step):
        # 包含销量和星期特征
        X.append(data[i:(i + time_step), :])
        # 只预测销量
        y.append(data[i + time_step, 0])
    return np.array(X), np.array(y)


# 设置时间步长（用过去7天预测第8天）
time_step = 7
X, y = create_dataset(scaled_data, time_step)

# 4. 划分训练集和测试集（最后3天作为测试）
train_size = len(X) - 3
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

# 5. 构建LSTM模型（解决警告问题）
model = Sequential([
    Input(shape=(time_step, 2)),  # 明确指定输入层
    LSTM(64, return_sequences=True),
    LSTM(32),
    Dense(16, activation='relu'),
    Dense(1)
])

model.compile(optimizer=Adam(learning_rate=0.001),
              loss='mean_squared_error')

# 6. 添加早停法防止过拟合
from keras.callbacks import EarlyStopping

early_stop = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)

# 7. 训练模型
history = model.fit(X_train, y_train,
                    batch_size=4,
                    epochs=200,
                    validation_data=(X_test, y_test),
                    callbacks=[early_stop],
                    verbose=1)

# 8. 预测
train_predict = model.predict(X_train)
test_predict = model.predict(X_test)

# 9. 反归一化（仅销量部分）
# 创建临时数组用于反归一化
temp_train = np.zeros((len(train_predict), 2))
temp_train[:, 0] = train_predict[:, 0]
train_predict = scaler.inverse_transform(temp_train)[:, 0]

temp_test = np.zeros((len(test_predict), 2))
temp_test[:, 0] = test_predict[:, 0]
test_predict = scaler.inverse_transform(temp_test)[:, 0]

# 实际值反归一化
y_train_actual = scaler.inverse_transform(
    np.column_stack((y_train, data['DayOfWeek'].values[time_step:time_step + len(y_train)])))[:, 0]
y_test_actual = scaler.inverse_transform(np.column_stack((y_test, data['DayOfWeek'].values[-len(y_test):])))[:, 0]

# 10. 可视化结果
plt.figure(figsize=(12, 6))

# 原始数据
plt.plot(data.index, data['Sales'], 'b-', label='Actual Sales')

# 训练集预测结果
train_dates = data.index[time_step:time_step + len(train_predict)]
plt.plot(train_dates, train_predict, 'g--', label='Training Prediction')

# 测试集预测结果
test_dates = data.index[-len(test_predict):]
plt.plot(test_dates, test_predict, 'r--', linewidth=2, label='Testing Prediction')

plt.title('Sales Prediction using LSTM')
plt.xlabel('Date')
plt.ylabel('Sales')
plt.legend()
plt.xticks(rotation=45)
plt.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()

# 11. 评估模型
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error

print("Training Performance:")
print(f"MAE: {mean_absolute_error(y_train_actual, train_predict):.2f}")
print(f"MAPE: {mean_absolute_percentage_error(y_train_actual, train_predict) * 100:.2f}%")

print("\nTesting Performance:")
print(f"MAE: {mean_absolute_error(y_test_actual, test_predict):.2f}")
print(f"MAPE: {mean_absolute_percentage_error(y_test_actual, test_predict) * 100:.2f}%")

# 12. 预测未来3天
last_sequence = scaled_data[-time_step:]  # 取最后7天数据
future_predictions = []
future_dates = []

for i in range(1, 4):
    # 创建输入序列
    current_input = last_sequence.reshape((1, time_step, 2))

    # 预测下一天销量
    next_pred = model.predict(current_input, verbose=0)[0, 0]

    # 创建新日期
    new_date = data.index[-1] + pd.Timedelta(days=i)
    future_dates.append(new_date)

    # 获取星期几（0=周一，6=周日）
    day_of_week = new_date.dayofweek

    # 创建新数据点（包含预测销量和星期几）
    new_data_point = np.array([[next_pred, day_of_week]])

    # 更新序列：移除最早数据，添加新预测
    last_sequence = np.vstack([last_sequence[1:], new_data_point])

    # 反归一化销量预测
    denorm_pred = scaler.inverse_transform([[next_pred, day_of_week]])[0, 0]
    future_predictions.append(denorm_pred)

print("\nFuture Predictions:")
for date, pred in zip(future_dates, future_predictions):
    print(f"{date.date()}: {pred:.2f} units")

# 13. 可视化训练过程
plt.figure(figsize=(10, 5))
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Model Training History')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend()
plt.grid(True)
plt.show()